#!/bin/bash
{
	#////////////////////////////////////
	# DietPi Services Script
	#
	#////////////////////////////////////
	# Created by Daniel Knight / daniel.knight@dietpi.com / dietpi.com
	#
	# Info:
	# - Allows service control for all listed programs available through dietpi-software
	# - By default known services are "disabled", thus not started by systemd, but
	#   started by /boot/dietpi/postboot at later boot stage.
	#
	USAGE='
Usage: dietpi-services [<command>] [<service>]
Available commands:
  <empty>		Interactive menu to apply service modes and settings
  status		Print service status info
  start			Start service
  stop			Stop service
  restart		Restart service
  dietpi_controlled	Let DietPi control start on boot
  systemd_controlled	Let systemd control start on boot
  enable, unmask	Enable service to allow its startup
  disable, mask		Disable service to prevent its startup completely
Available services:
  <service_name>	Apply command to a single available systemd or sysvinit service
  <empty>		Apply command to all available services known to DietPi
  			NB: Services required for network or shell session will be skipped.
			NB: You can include/exclude services by editing the following file:
			    - /boot/dietpi/.dietpi-services_include_exclude
'	#////////////////////////////////////

	# Grab Inputs
	readonly INPUT_CMD=$1
	# - Pre-v6.25 compatibility: all => <empty>
	[[ $2 && $2 != 'all' ]] && readonly INPUT_SERVICE=$2

	# - Optional env vars to prevent service control
	[[ $G_DIETPI_SERVICES_DISABLE == 1 ]] && exit 0
	[[ $DISABLE_SERVICES_START == 1 ]] && [[ $INPUT_CMD == 'start' || $INPUT_CMD == 'restart' ]] && exit 0

	# Import DietPi-Globals --------------------------------------------------------------
	. /boot/dietpi/func/dietpi-globals
	readonly G_PROGRAM_NAME='DietPi-Services'
	G_CHECK_ROOT_USER
	G_INIT
	# Import DietPi-Globals --------------------------------------------------------------

	#/////////////////////////////////////////////////////////////////////////////////////
	# Service Control
	#/////////////////////////////////////////////////////////////////////////////////////
	Load_All_Services_Array(){

		aSERVICE_NAME=(

			# Core ---------------------------------------------------------------
			# - Network
			'avahi-daemon'
			'isc-dhcp-server'
			'haproxy'

			# - File servers
			'proftpd'
			'vsftpd'
			'nmbd' 'smbd'
			'nfs-kernel-server'

			# Backends -----------------------------------------------------------
			# - Databases
			'redis-server'
			'mariadb'

			# - PHP
			'php7.2-fpm'
			'php7.3-fpm'
			'php7.4-fpm'
			'php8.0-fpm'

			# - Webservers
			'apache2'
			'nginx'
			'lighttpd'
			'tomcat8'

			# - Media
			'alsa-init' # Google AI
			'coturn'
			'mpd'
			'minidlna'
			'shairport-sync'
			'squeezelite'
			'gmrender'
			'mumble-server'
			'networkaudiod'
			'roonbridge'
			'roonserver'
			'roon-extension-manager'
			'icecast2' 'darkice'
			'voice-recognizer'

			# - Download/BitTorrent
			'transmission-daemon'
			'qbittorrent'
			'rtorrent'
			'nzbget'
			'deluged'

			# Frontends / Misc ---------------------------------------------------
			# - Media
			'ympd'
			'mympd'
			'logitechmediaserver'
			'subsonic'
			'airsonic'
			'mopidy'
			'koel'
			'raspotify'
			'tautulli'
			'plexmediaserver'
			'emby-server'
			'spotify-connect-web'
			'ubooquity'
			'komga'
			'jellyfin'

			# - Download/BitTorrent
			'medusa'
			'aria2'
			'sabnzbd'
			'couchpotato'
			'jackett'
			'sonarr'
			'radarr'
			'lidarr'
			'bazarr'
			'deluge-web'

			# - Cloud/Backups
			'bdd'
			'minio'
			'syncthing' 'syncthing-inotify'
			'urbackupsrv'
			'gogs'
			'gitea'
			'firefox-sync'
			'bitwarden_rs'

			# - Emulation/Gaming
			'mineos'
			'nukkit'
			'cuberite'
			'papermc'

			# - Camera/Surveillance
			'motioneye'
			'raspimjpeg'
			'mjpg-streamer'

			# - Printing
			'octoprint'

			# - Social/Search
			'yacy'
			'openbazaar'

			# - Hardware Projects
			'pi-spc'
			'pijuice'
			'mosquitto'
			'node-red'
			'blynkserver'
			'webiopi'
			'emonhub'
			'influxdb'
			'grafana-server'

			# - Home Automation
			'home-assistant'
			'domoticz'

			# - Network
			'noip2'
			'virtualhere'
			'tor'
			'hostapd'

			# - System stats / Management
			'netdata'
			'rpimonitor'
			'webmin'
			'htpc-manager'

			# - Misc
			'docker'
			'mycroft'
			'fahclient'
			'cron'

		)

		# Additional services: https://github.com/MichaIng/DietPi/issues/1869#issuecomment-401017251
		[[ -f '/etc/rsyncd.conf' ]] && aSERVICE_NAME+=('rsync')

		# Non-controlled services: Only show in menu and/or status mode!
		if [[ ! $INPUT_CMD || $INPUT_CMD == 'status' ]]; then

			# SSH
			aSERVICE_NAME+=('dropbear')
			aSERVICE_NAME+=('ssh') 				# OpenSSH Server

			# Misc
			aSERVICE_NAME+=('fail2ban')			# Brute-force protection, which should not be stopped during updates/installs etc
			#aSERVICE_NAME+=('systemd-timesyncd') 		# Network time sync: DietPi stops this by default after success, may confuse user/prompt questions.
			aSERVICE_NAME+=('pihole-FTL')			# https://github.com/MichaIng/DietPi/issues/1696
			aSERVICE_NAME+=('openvpn') 			# https://github.com/MichaIng/DietPi/issues/1501
			aSERVICE_NAME+=('vncserver')			# DietPi VNC Server
			aSERVICE_NAME+=('nxserver')			# NoMachine Server
			aSERVICE_NAME+=('xrdp') 			# XRDP Server
			aSERVICE_NAME+=('amiberry')			# DietPi Amiberry service
			aSERVICE_NAME+=('unbound')			# Unbound DNS server, could be currently used by machine
			#aSERVICE_NAME+=('wg-quick@wg0')		# WireGuard: Currently instantiated services are not supported

			# DietPi
			aSERVICE_NAME+=('dietpi-nordvpn') 		# NordVPN install + client
			if [[ $INPUT_CMD == 'status' ]]; then

				aSERVICE_NAME+=('dietpi-ramlog')
				aSERVICE_NAME+=('dietpi-preboot')
				aSERVICE_NAME+=('dietpi-boot')
				aSERVICE_NAME+=('dietpi-postboot')
				aSERVICE_NAME+=('dietpi-wifi-monitor') 	# https://github.com/MichaIng/DietPi/issues/1288#issuecomment-350653480
				aSERVICE_NAME+=('dietpi-arr_to_RAM')	# Sonarr/Radarr/Lidarr database to RAM link service

			fi

		fi

		Process_Includes_Excludes

	}

	# Apply custom include/exclude choices
	readonly FP_INCLUDE_EXCLUDE='/boot/dietpi/.dietpi-services_include_exclude'
	Process_Includes_Excludes(){

		[[ -f $FP_INCLUDE_EXCLUDE ]] || cat << '_EOF_' > $FP_INCLUDE_EXCLUDE
# DietPi-Services Include/Exclude configuration

# Include custom service (Use '+ servicename' without the comments to enable DietPi control of that service)
#	Once completed, for DietPi to control the service, please run the following command, without quotes (')
#	'dietpi-services dietpi_controlled'
#+ myservice1
#+ myservice2

# Exclude DietPi from controlling known services (Use '- servicename' without the comments to disable DietPi control for that service)
#	The service will be in disabled form, and, you can start and stop it manually
#- cron
#- transmission-daemon

_EOF_
		aSERVICE_EXCLUDED=()
		local i

		while read -r line
		do

			# Include
			if [[ $line == '+ '* ]]; then

				line=${line: +2}

				# - Skip known services
				for i in "${aSERVICE_NAME[@]}"
				do

					[[ $line == "$i" ]] && continue 2

				done

				[[ $G_DEBUG == 1 ]] && G_DIETPI-NOTIFY 2 "Including custom service: $line"
				aSERVICE_NAME+=("$line")

			# Exclude
			elif [[ $line == '- '* ]]; then

				line=${line: +2}

				for i in "${!aSERVICE_NAME[@]}"
				do

					[[ $line == "${aSERVICE_NAME[$i]}" ]] || continue

					# Show in menu, but mark as excluded
					if [[ $INPUT_CMD ]]; then

						[[ $G_DEBUG == 1 ]] && G_DIETPI-NOTIFY 2 "Excluding service: $line"
						unset -v "aSERVICE_NAME[$i]"

					else

						aSERVICE_EXCLUDED[$i]=1

					fi

					break

				done

			fi

		done < $FP_INCLUDE_EXCLUDE

	}

	# Load array of available/chosen services
	Load_Service_Array(){

		[[ $G_DEBUG == 1 ]] && G_DIETPI-NOTIFY 2 'Generating service list, please wait...'

		# Load all services array, skip in case of single service input
		# shellcheck disable=SC2015
		[[ $INPUT_SERVICE ]] && aSERVICE_NAME=("$INPUT_SERVICE") || Load_All_Services_Array

		# Check service availability
		aFP_SERVICE=()
		aSERVICE_MODE=() # enabled/disabled/masked
		local i j
		for i in "${!aSERVICE_NAME[@]}"
		do

			[[ ${aSERVICE_NAME[$i]} ]] || { unset -v "aSERVICE_NAME[$i]"; continue; } # Failsafe

			# Check for sysvinit services from systemd-sysv-generator dir: /run/systemd/generator.late/
			for j in /{{etc,usr/local/lib,lib,usr/lib}/systemd/system,run/systemd/generator.late}/"${aSERVICE_NAME[$i]}.service"
			do

				[[ -e $j ]] || continue

				# Check masked state, in case continue to catch real file
				[[ -L $j && $(readlink -m "$j") == '/dev/null' ]] && aSERVICE_MODE[$i]='masked' && continue

				aFP_SERVICE[$i]=$j
				break

			done

			# Remove non-available services from array
			[[ ${aFP_SERVICE[$i]} ]] || unset -v "aSERVICE_NAME[$i]"

		done

	}

	# $1 = command
	# $2 = service name
	# $3 = exit code
	Print_Status(){ G_DIETPI-NOTIFY "${3/[^0]*/1}" "$1 : $2"; }

	# $1 = command (start/stop/restart)
	# $2 = service index (optional)
	Set_Running_State(){

		local command=${1,,}
		local index=$2
		local services i

		# Add and order services to be handled
		# - Single service input
		if [[ $index ]]; then

			services=$index

		# - stop: Reverse service order
		elif [[ $command == 'stop' ]]; then

			for i in "${!aSERVICE_NAME[@]}"
			do

				services="$i $services"

			done

		# - start/restart: Standard service order
		else

			services=${!aSERVICE_NAME[*]}

		fi

		# Enable ownCloud and Nextcloud maintenance mode before all services being stopped or restarted
		if [[ $command == 'stop' || $command == 'restart' ]] && [[ ! $index ]]; then

			[[ -f '/var/www/owncloud/config/config.php' ]] && grep -q "'maintenance' => false," /var/www/owncloud/config/config.php && G_EXEC_NOHALT=1 G_EXEC occ maintenance:mode --on
			[[ -f '/var/www/nextcloud/config/config.php' ]] && grep -q "'maintenance' => false," /var/www/nextcloud/config/config.php && G_EXEC_NOHALT=1 G_EXEC ncc maintenance:mode --on

		fi

		# Apply command
		for i in $services
		do

			[[ ${aSERVICE_MODE[$i]} == 'masked' ]] && { G_DIETPI-NOTIFY 2 "skip : ${aSERVICE_NAME[$i]} (due to mask)"; continue; }
			G_DIETPI-NOTIFY -2 "${aSERVICE_NAME[$i]}"
			systemctl "$command" "${aSERVICE_NAME[$i]}" &> /dev/null
			Print_Status "$command" "${aSERVICE_NAME[$i]}" $?

		done

		# Disable ownCloud and Nextcloud maintenance mode after all services being started or restarted
		if [[ $command == 'start' || $command == 'restart' ]] && [[ ! $index ]]; then

			[[ -f '/var/www/owncloud/config/config.php' ]] && grep -q "'maintenance' => true," /var/www/owncloud/config/config.php && G_EXEC_NOHALT=1 G_EXEC occ maintenance:mode --off
			[[ -f '/var/www/nextcloud/config/config.php' ]] && grep -q "'maintenance' => true," /var/www/nextcloud/config/config.php && G_EXEC_NOHALT=1 G_EXEC ncc maintenance:mode --off

		fi

	}

	# $1 = command
	# $2 = service index (optional)
	Apply_Service_State(){

		local command=${1,,}
		local index=$2
		local services i

		[[ $index ]] && services=$index || services=${!aSERVICE_NAME[*]}

		# start/stop/restart
		if [[ $command == 'start' || $command == 'stop' || $command == 'restart' ]]; then

			Set_Running_State "$command" "$index"

		# status
		elif [[ $command == 'status' ]]; then

			local status_full space status
			for i in $services
			do

				status_full=$(systemctl -l --no-pager status "${aSERVICE_NAME[$i]}")
				# Align status output
				space='\t'
				(( ${#aSERVICE_NAME[$i]} < 13 )) && space+='\t'; (( ${#aSERVICE_NAME[$i]} < 5 )) && space+='\t'
				status="${aSERVICE_NAME[$i]}${space}$(mawk '/Active/{print substr($0,12);exit}' <<< "$status_full")"
				if [[ $status == *'failed'* ]]; then

					G_DIETPI-NOTIFY 1 "$status_full"

				elif [[ $status == *'inactive'* ]]; then

					G_DIETPI-NOTIFY 2 "$status"

				else

					G_DIETPI-NOTIFY 0 "$status"

				fi

			done

		# dietpi_controlled/systemd_controlled/mask/unmask/enable/disable
		else

			local systemctl_cmd
			if [[ $command == 'dietpi_controlled' ]]; then

				systemctl_cmd='disable'

			elif [[ $command == 'systemd_controlled' ]]; then

				systemctl_cmd='enable'

			elif [[ $command == 'enable' || $command == 'unmask' ]]; then

				systemctl_cmd='unmask'

			elif [[ $command == 'disable' || $command == 'mask' ]]; then

				systemctl_cmd='mask'
				# Disable and stop services before masking them
				Apply_Service_State dietpi_controlled "$services"
				Set_Running_State stop "$services"

			else

				G_DIETPI-NOTIFY 1 "Invalid input command ($command). Aborting...\n$USAGE"
				exit 1

			fi

			# Apply command
			for i in $services
			do

				# Skip masked services, if not to be unmasked
				[[ ${aSERVICE_MODE[$i]} == 'masked' && $systemctl_cmd != 'unmask' ]] && { G_DIETPI-NOTIFY 2 "skip : ${aSERVICE_NAME[$i]} (due to mask)"; continue; }
				systemctl $systemctl_cmd "${aSERVICE_NAME[$i]}" &> /dev/null
				Print_Status "$command" "${aSERVICE_NAME[$i]}" $?

			done

		fi

	}

	#/////////////////////////////////////////////////////////////////////////////////////
	# Process Tool
	#/////////////////////////////////////////////////////////////////////////////////////
	# $1 = service index
	# $2 = setting (reset=reset ALL settings, 0=CPUAffinity, 1=CPUSchedulingPolicy, 2=Nice, 3=CPUSchedulingPriority, 4=IOSchedulingClass, 5=IOSchedulingPriority)
	# $3 = value (reset=reset $2 setting)
	readonly FP_PROCESS_TOOL_CONF='dietpi-process_tool.conf'
	Apply_Process_Tool(){

		local index=$1
		local setting=$2
		local value=${3,,}
		local dp="/etc/systemd/system/${aSERVICE_NAME[$index]}.service.d"
		local fp="$dp/$FP_PROCESS_TOOL_CONF"
		local i

		# Arrays to translate $2 integer to settings names
		local asetting=('CPUAffinity' 'CPUSchedulingPolicy' 'Nice' 'CPUSchedulingPriority' 'IOSchedulingClass' 'IOSchedulingPriority')
		local aarray=('aCPU_AFFINITY' 'aCPU_POLICY' 'aCPU_NICE' 'aCPU_PRIORITY' 'aIO_CLASS' 'aIO_PRIORITY')

		# Reset priority values if scheduling policy/class is changed
		if [[ $setting == 1 ]]; then

			Apply_Process_Tool "$index" 2 reset
			Apply_Process_Tool "$index" 3 reset

		elif [[ $setting == 4 ]]; then

			Apply_Process_Tool "$index" 5 reset

		fi

		# Reset all process tool settings
		if [[ $setting == 'reset' ]]; then

			for i in {0..5}; do unset -v "${aarray[$i]}[$index]"; done
			[[ -f $fp ]] && G_EXEC rm "$fp"
			[[ -d $dp ]] && G_EXEC rmdir --ignore-fail-on-non-empty "$dp"

		# Reset single process tool setting
		elif [[ $value == 'reset' ]]; then

			unset -v "${aarray[$setting]}[$index]"
			[[ -f $fp ]] && G_EXEC sed -i "/^${asetting[$setting]}=/d" "$fp"

		# Apply process tool setting
		else

			[[ -d $dp ]] || mkdir -p "$dp"
			[[ -f $fp ]] || echo -e '# WARNING: Do not manually edit this file, use "dietpi-services" to adjust values!\n[Service]' > "$fp"
			declare -ag "${aarray[$setting]}[$index]=$value"
			G_CONFIG_INJECT "${asetting[$setting]}=" "${asetting[$setting]}=$value" "$fp"

		fi

		SYSTEMD_RELOAD_REQUIRED=1
		aSERVICE_RESTART_REQUIRED[$index]=1

	}

	# $1 = service index
	Load_Process_Tool(){

		local index=$1
		local fp="/etc/systemd/system/${aSERVICE_NAME[$index]}.service.d/$FP_PROCESS_TOOL_CONF"
		[[ -f $fp ]] || return

		# Source values from config file
		# - [Service] line throws an effectless error that we can simply hide.
		# - All values are single word strings, so bash assigns them correctly.
		local CPUAffinity CPUSchedulingPolicy Nice CPUSchedulingPriority IOSchedulingClass IOSchedulingPriority
		. "$fp" &> /dev/null

		[[ $CPUAffinity ]] && aCPU_AFFINITY[$index]=$CPUAffinity
		[[ $CPUSchedulingPolicy ]] && aCPU_POLICY[$index]=$CPUSchedulingPolicy
		[[ $Nice ]] && aCPU_NICE[$index]=$Nice
		[[ $CPUSchedulingPriority ]] && aCPU_PRIORITY[$index]=$CPUSchedulingPriority
		[[ $IOSchedulingClass ]] && aIO_CLASS[$index]=$IOSchedulingClass
		[[ $IOSchedulingPriority ]] && aIO_PRIORITY[$index]=$IOSchedulingPriority

	}

	Load_Process_Tool_Arrays(){

		SYSTEMD_RELOAD_REQUIRED=0 # Set to 1 after any systemd unit/drop-in changes, apply on exit or running state changes
		aSERVICE_RESTART_REQUIRED=() # Set to 1 after single systemd unit/drop-in changes, ask to apply on exit

		aCPU_NICE=()
		aCPU_AFFINITY=()
		aCPU_POLICY=()
		aCPU_PRIORITY=()
		aIO_CLASS=()
		aIO_PRIORITY=()

		# https://manpages.debian.org/stretch/manpages/sched.7.en.html
		aCPU_POLICY_TYPE=('fifo' 'rr' 'other' 'batch' 'idle')
		aCPU_POLICY_DESC=('First In, First Out (Real-time, time-critical)' 'Round Robin (Real-time, time-critical)' 'Normal (Default)' 'Batch style execution' 'Background Jobs (Very low priority)')

		# https://manpages.debian.org/stretch/util-linux/ionice.1.en.html
		aIO_CLASS_TYPE=('realtime' 'best-effort' 'idle')
		aIO_CLASS_DESC=('Time-critical (Highest priority)' 'Normal (Default)' 'Background Jobs (Very low priority)')

		for i in "${!aSERVICE_NAME[@]}"
		do

			aSERVICE_MODE[$i]=$(systemctl is-enabled "${aSERVICE_NAME[$i]}" 2> /dev/null)
			Load_Process_Tool "$i"

		done

	}

	#/////////////////////////////////////////////////////////////////////////////////////
	# Menu System
	#/////////////////////////////////////////////////////////////////////////////////////
	MENU_TARGETID=0
	MENU_SERVICE_INDEX=-1
	MENU_LAST_SELECTED_MAIN= # Main menu
	MENU_LAST_SELECTED_SUB_0= # Sub menus, level 0

	Menu_Exit(){

		G_WHIP_SIZE_X_MAX=50
		if G_WHIP_YESNO "Exit $G_PROGRAM_NAME?"; then

			# Reload systemd, if required
			(( $SYSTEMD_RELOAD_REQUIRED )) && G_EXEC_NOHALT=1 G_EXEC systemctl daemon-reload

			# Prompt to restart changed services
			local service_restart_list_menu aservice_restart_list_systemd=() i
			for i in "${!aSERVICE_RESTART_REQUIRED[@]}"
			do

				service_restart_list_menu+="\n - ${aSERVICE_NAME[$i]}: "
				[[ ${aCPU_AFFINITY[$i]} ]] && service_restart_list_menu+="Affinity=${aCPU_AFFINITY[$i]} | "
				[[ ${aCPU_POLICY[$i]} ]] && service_restart_list_menu+="CPU Scheduling Policy=${aCPU_POLICY[$i]} | "
				[[ ${aCPU_NICE[$i]} ]] && service_restart_list_menu+="Nice=${aCPU_NICE[$i]} | "
				[[ ${aCPU_PRIORITY[$i]} ]] && service_restart_list_menu+="CPU Scheduling Priority=${aCPU_PRIORITY[$i]} | "
				[[ ${aIO_CLASS[$i]} ]] && service_restart_list_menu+="I/O Scheduling Class=${aIO_CLASS[$i]} | "
				[[ ${aIO_PRIORITY[$i]} ]] && service_restart_list_menu+="I/O Scheduling Priority=${aIO_PRIORITY[$i]} | "
				aservice_restart_list_systemd+=("${aSERVICE_NAME[$i]}")

			done

			if [[ ${aservice_restart_list_systemd[0]} ]] && G_WHIP_YESNO "[ INFO ] The following services require a restart, in order to apply your chosen settings:${service_restart_list_menu%[|:] }
\nDo you wish to restart the above services now?"; then

				G_EXEC systemctl restart "${aservice_restart_list_systemd[@]}"

			fi

			MENU_TARGETID=-1 # Exit

		else

			MENU_TARGETID=0 # Return to main menu

		fi

	}

	# MENU_TARGETID=0
	Menu_Main(){

		local i

		G_WHIP_MENU_ARRAY=('' '●─ Single Service Options ')
		for i in "${!aSERVICE_NAME[@]}"
		do

			G_WHIP_MENU_ARRAY+=("${aSERVICE_NAME[$i]}" ": $(systemctl is-active "${aSERVICE_NAME[$i]}") | Affinity ${aCPU_AFFINITY[$i]:-0-$(( $G_HW_CPU_CORES - 1 ))}")

		done
		G_WHIP_MENU_ARRAY+=(

			'Add' ': Add missing service to DietPi-Services'
			'' '●─ Global Service Options '
			'Stop' ': Stop ALL above services'
			'Restart' ': Start/restart ALL above services'

		)

		G_WHIP_DEFAULT_ITEM=$MENU_LAST_SELECTED_MAIN
		G_WHIP_BUTTON_CANCEL_TEXT='Exit'
		if G_WHIP_MENU 'Please select an option or program:'; then

			MENU_LAST_SELECTED_MAIN=$G_WHIP_RETURNED_VALUE

			if [[ $G_WHIP_RETURNED_VALUE == 'Add' ]]; then

				local not_found new_index
				# Find first empty index
				for ((i=0;i<=${#aSERVICE_MODE[@]};i++))
				do

					 [[ ${aSERVICE_NAME[$i]} ]] || { new_index=$i; break; }

				done
				while G_WHIP_INPUTBOX "${not_found}Please add the name of the service to be added, without the \".service\" file ending:"
				do

					if [[ $G_WHIP_RETURNED_VALUE ]]; then

						# Check for known services
						for i in "${aSERVICE_NAME[@]}"
						do

							[[ $G_WHIP_RETURNED_VALUE == "$i" ]] || continue

							not_found="[FAILED] The service name you entered ($G_WHIP_RETURNED_VALUE) is already handled by DietPi-Services. Please retry or cancel.\n\n"
							continue 2

						done

						for i in /{{etc,usr/local/lib,lib,usr/lib}/systemd/system,run/systemd/generator.late}/"$G_WHIP_RETURNED_VALUE.service"
						do

							[[ -f $i ]] || continue

							aSERVICE_NAME[$new_index]=$G_WHIP_RETURNED_VALUE
							aFP_SERVICE[$new_index]=$i
							aSERVICE_MODE[$new_index]=$(systemctl is-enabled "$G_WHIP_RETURNED_VALUE" 2> /dev/null)
							Load_Process_Tool "$new_index"
							G_CONFIG_INJECT "+ ${aSERVICE_NAME[$new_index]}" "+ ${aSERVICE_NAME[$new_index]}" $FP_INCLUDE_EXCLUDE
							break 2

						done

					fi

					not_found="[FAILED] Could not find a service file that matches your input ($G_WHIP_RETURNED_VALUE). Please retry or cancel.\n\n"

				done

			elif [[ $G_WHIP_RETURNED_VALUE == 'Restart' || $G_WHIP_RETURNED_VALUE == 'Stop' ]]; then

				(( $SYSTEMD_RELOAD_REQUIRED )) && G_EXEC systemctl daemon-reload && SYSTEMD_RELOAD_REQUIRED=0
				Set_Running_State "$G_WHIP_RETURNED_VALUE"
				aSERVICE_RESTART_REQUIRED=()
				sleep 0.5

			elif [[ $G_WHIP_RETURNED_VALUE ]]; then

				# Find selected program index
				MENU_SERVICE_INDEX=-1
				for i in "${!aSERVICE_NAME[@]}"
				do

					[[ ${aSERVICE_NAME[$i]} == "$G_WHIP_RETURNED_VALUE" ]] || continue
					MENU_SERVICE_INDEX=$i
					break

				done
				(( $MENU_SERVICE_INDEX >= 0 )) && MENU_TARGETID=1

			fi

		else

			Menu_Exit

		fi

	}

	# MENU_TARGETID=1
	Menu_Service(){

		local service_state=$(systemctl is-active "${aSERVICE_NAME[$MENU_SERVICE_INDEX]}" 2> /dev/null)
		local service_mode_print=${aSERVICE_MODE[$MENU_SERVICE_INDEX]}
		if [[ $service_mode_print == 'disabled' ]]; then

			service_mode_print='DietPi controlled'

		elif [[ $service_mode_print == 'enabled' ]]; then

			service_mode_print='Systemd controlled'

		fi
		local service_exclusion='included'
		[[ ${aSERVICE_EXCLUDED[MENU_SERVICE_INDEX]} == 1 ]] && service_exclusion='excluded'
		local cpu_affinity=${aCPU_AFFINITY[$MENU_SERVICE_INDEX]:-0-$(( $G_HW_CPU_CORES - 1 ))}
		local cpu_policy=${aCPU_POLICY[$MENU_SERVICE_INDEX]:-other}
		local cpu_nice=${aCPU_NICE[$MENU_SERVICE_INDEX]:-0}
		local cpu_priority=${aCPU_PRIORITY[$MENU_SERVICE_INDEX]:-1}
		local io_class=${aIO_CLASS[$MENU_SERVICE_INDEX]:-best-effort}
		local io_priority_default=0
		[[ $io_class == 'best-effort' ]] && io_priority_default=$(( ( $cpu_nice + 20 ) / 5 ))
		local io_priority=${aIO_PRIORITY[$MENU_SERVICE_INDEX]:-$io_priority_default}

		G_WHIP_MENU_ARRAY=(

			'' '●─ Service Control '
			'State' ": [$service_state]"
			'Mode' ": [$service_mode_print]"
			'Include/Exclude' ": [$service_exclusion]"
			'Status' ': Display systemd status log'

		)
		if [[ $service_mode_print != 'masked' ]]; then

			G_WHIP_MENU_ARRAY+=(

				'Edit' ": [${aFP_SERVICE[$MENU_SERVICE_INDEX]}]"
				'' '●─ Process Tool '
				'CPU Affinity' ": [$cpu_affinity]"
				'CPU Scheduling Policy' ": [$cpu_policy]"

			)
			# Real-time CPU schedulers use CPU priority while "other" and "batch" use nice and "idle" none of them: https://manpages.debian.org/stretch/manpages/sched.7.en.html
			if [[ $cpu_policy == 'other' || $cpu_policy == 'batch' ]]; then

				G_WHIP_MENU_ARRAY+=('CPU Nice' ": [$cpu_nice]")

			elif [[ $cpu_policy == 'fifo' || $cpu_policy == 'rr' ]]; then

				G_WHIP_MENU_ARRAY+=('CPU Scheduling Priority' ": [$cpu_priority]")

			fi
			G_WHIP_MENU_ARRAY+=('I/O Scheduling Class' ": [$io_class]")
			# idle IO scheduler does not use IO priority: https://manpages.debian.org/stretch/util-linux/ionice.1.en.html
			[[ $io_class != 'idle' ]] && G_WHIP_MENU_ARRAY+=('I/O Scheduling Priority' ": [$io_priority]")
			G_WHIP_MENU_ARRAY+=(

				'' '●─ Presets '
				'Reset' ': Resets all CPU/IO settings to system defaults'
				'Highest Priority' ': Highest priority realtime CPU/IO preset'
				'High Priority' ': High priority CPU/IO preset'
				'Low Priority' ': Low priority CPU/IO preset'
				'Lowest Priority' ': Lowest priority background CPU/IO preset'

			)

		fi

		G_WHIP_DEFAULT_ITEM=$MENU_LAST_SELECTED_SUB_0
		G_WHIP_BUTTON_CANCEL_TEXT='Back'
		if G_WHIP_MENU "Please select an option for ${aSERVICE_NAME[$MENU_SERVICE_INDEX]}:"; then

			MENU_LAST_SELECTED_SUB_0=$G_WHIP_RETURNED_VALUE

			case "$G_WHIP_RETURNED_VALUE" in

				'State')

					local command='restart'
					[[ ${service_state,,} == 'active' ]] && command='stop'

					(( $SYSTEMD_RELOAD_REQUIRED )) && G_EXEC systemctl daemon-reload && SYSTEMD_RELOAD_REQUIRED=0
					Set_Running_State "$command" "$MENU_SERVICE_INDEX"
					unset -v "aSERVICE_RESTART_REQUIRED[$MENU_SERVICE_INDEX]"
					sleep 0.5

				;;

				'Mode')

					G_WHIP_MENU_ARRAY=(

						'DietPi controlled' ': DietPi controls start on boot (Recommended)'
						'Systemd controlled' ': Systemd controls start on boot'
						'mask'	': Hide service from system and prevent use'
						'unmask' ': Un-hide service from system and allow use'

					)

					G_WHIP_DEFAULT_ITEM='DietPi controlled'
					if G_WHIP_MENU "Please select the desired Service Mode for: ${aSERVICE_NAME[$MENU_SERVICE_INDEX]}"; then

						local command=$G_WHIP_RETURNED_VALUE
						# Unmask services before enable/disable
						[[ $command == *'controlled' ]] && Apply_Service_State unmask "$MENU_SERVICE_INDEX"
						Apply_Service_State "${command/ /_}" "$MENU_SERVICE_INDEX"
						# Re-estimate service mode
						aSERVICE_MODE[$MENU_SERVICE_INDEX]=$(systemctl is-enabled "${aSERVICE_NAME[$MENU_SERVICE_INDEX]}" 2> /dev/null)

					fi

				;;

				'Include/Exclude')

					G_WHIP_MENU_ARRAY=(

						'Include' ': Allow DietPi auto-control'
						'Exclude' ': Deny DietPi auto-control'

					)

					G_WHIP_DEFAULT_ITEM='Include'
					if G_WHIP_MENU "Please choose whether to include or exclude ${aSERVICE_NAME[$MENU_SERVICE_INDEX]} from automated DietPi-Services control.
This affects starts/stops/restarts during DietPi-Software installs, DietPi-Update, DietPi-Backup and similar maintenance tasks."; then

						if [[ $G_WHIP_RETURNED_VALUE == 'Include' ]]; then

							# If service is listed here, it was excluded via include/exclude file.
							sed -i "/^- ${aSERVICE_NAME[$MENU_SERVICE_INDEX]}/d" $FP_INCLUDE_EXCLUDE
							aSERVICE_EXCLUDED[MENU_SERVICE_INDEX]=0

						else

							# Remove include entry, if existent, else add exclude entry.
							if grep -q "^+ ${aSERVICE_NAME[$MENU_SERVICE_INDEX]}" $FP_INCLUDE_EXCLUDE; then

								sed -i "/^+ ${aSERVICE_NAME[$MENU_SERVICE_INDEX]}/d" $FP_INCLUDE_EXCLUDE
								unset -v "aSERVICE_NAME[$MENU_SERVICE_INDEX]"
								# Service needs to be re-added from main menu
								MENU_TARGETID=0 # Return to main menu

							else

								G_CONFIG_INJECT "- ${aSERVICE_NAME[$MENU_SERVICE_INDEX]}" "- ${aSERVICE_NAME[$MENU_SERVICE_INDEX]}" $FP_INCLUDE_EXCLUDE
								# Keep in menu like other excluded known services
								aSERVICE_EXCLUDED[MENU_SERVICE_INDEX]=1

							fi

						fi

					fi

				;;

				'Status')

					systemctl -l --no-pager status "${aSERVICE_NAME[$MENU_SERVICE_INDEX]}" &> systemctl.log
					log=0 G_WHIP_VIEWFILE systemctl.log
					rm systemctl.log

				;;

				'Edit')

					local dp="/etc/systemd/system/${aSERVICE_NAME[$MENU_SERVICE_INDEX]}.service.d"
					local fp="$dp/dietpi-services_edit.conf"

					if [[ ! -f $fp ]]; then

						G_WHIP_YESNO "[ INFO ] To allow safe service editing, a drop-in config with commented original service file content will be created.\n
Please uncomment and edit only the lines that you need to change.\n\nTo undo changes, remove the drop-in, reload and restart the service:
 - rm $fp\n - systemctl daemon-reload\n - systemctl restart ${aSERVICE_NAME[$MENU_SERVICE_INDEX]}\n\nDo you want to continue?" || return
						[[ -d $dp ]] || mkdir -p "$dp"
						cp -a "${aFP_SERVICE[$MENU_SERVICE_INDEX]}" "$fp"
						sed -Ei 's/^([^[#])/#\1/' "$fp"

					fi
					nano "$fp"
					SYSTEMD_RELOAD_REQUIRED=1
					aSERVICE_RESTART_REQUIRED[$MENU_SERVICE_INDEX]=1

				;;

				'CPU Affinity')

					# ToDo: Preset selection based on current affinity
					G_WHIP_CHECKLIST_ARRAY=()
					for ((i=0; i<$G_HW_CPU_CORES; i++))
					do

						G_WHIP_CHECKLIST_ARRAY+=("$i" 'CPU                           ' 'on')

					done

					if G_WHIP_CHECKLIST "Please select the desired CPU Affinity for: ${aSERVICE_NAME[$MENU_SERVICE_INDEX]}\n
 - Use the spacebar to enable/disable access to specific cores, for this program.\n - The default value is to enable all items.\n - De-select all items to reset to system defaults."; then

						local new_affinity=
						for i in $G_WHIP_RETURNED_VALUE
						do

							# taskset requires comma-seperated CPU indices
							[[ $new_affinity ]] && new_affinity+=",$i" || new_affinity=$i

						done

						# Update affinity array with new value, if at least 1 item was selected, otherwise reset
						Apply_Process_Tool "$MENU_SERVICE_INDEX" 0 "${new_affinity:-reset}"

					fi

				;;

				'CPU Scheduling Policy')

					G_WHIP_MENU_ARRAY=('Reset' ': Reset CPU Scheduling Policy to system defaults')
					for i in "${!aCPU_POLICY_TYPE[@]}"
					do

						G_WHIP_MENU_ARRAY+=("${aCPU_POLICY_TYPE[$i]}" ": ${aCPU_POLICY_DESC[$i]}" )

					done

					G_WHIP_DEFAULT_ITEM=$cpu_policy
					if G_WHIP_MENU "Please select the desired CPU Scheduling Policy for: ${aSERVICE_NAME[$MENU_SERVICE_INDEX]}\n\nRead about CPU scheduling:
 - https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/performance_tuning_guide/s-cpu-scheduler"; then

						Apply_Process_Tool "$MENU_SERVICE_INDEX" 1 "$G_WHIP_RETURNED_VALUE"

					fi

				;;

				'CPU Nice')

					local desc

					G_WHIP_MENU_ARRAY=('Reset' ' Reset CPU Nice to system defaults')
					for i in {-20..19}
					do
						desc=
						if (( $i == -20 )); then

							desc='(Highest priority)'

						elif (( $i == -10 )); then

							desc='(Higher priority)'

						elif (( $i == -5 )); then

							desc='(High priority)'

						elif (( $i == 0 )); then

							desc='(Default priority)'

						elif (( $i == 5 )); then

							desc='(Low priority)'

						elif (( $i == 10 )); then

							desc='(Lower priority)'

						elif (( $i == 19 )); then

							desc='(Lowest priority)'

						fi

						# Note: Whiptail will not work with negative numbers. The string cannot start with "-" as it throws subscript error.
						G_WHIP_MENU_ARRAY+=("Nice : $i" " $desc")

					done

					G_WHIP_DEFAULT_ITEM="Nice : $cpu_nice"
					if G_WHIP_MENU "Please select the desired Nice level for: ${aSERVICE_NAME[$MENU_SERVICE_INDEX]}\n
Info:\n - Negative values have a higher priority (eg: -10).\n - Positive values have a lower priority (eg: 15).\n - The default value is 0."; then

						Apply_Process_Tool "$MENU_SERVICE_INDEX" 2 "${G_WHIP_RETURNED_VALUE##*: }" # Convert back to int

					fi

				;;

				'CPU Scheduling Priority')

					# 7 step description scale
					local scale_value_highest=99
					local scale_value_higher=$(( $scale_value_highest * 5/6 ))
					local scale_value_high=$(( $scale_value_highest * 2/3 ))
					local scale_value_medium=$(( $scale_value_highest * 1/2 ))
					local scale_value_low=$(( $scale_value_highest * 1/3 ))
					local scale_value_lower=$(( $scale_value_highest * 1/6 ))
					local scale_value_lowest=1
					local desc

					G_WHIP_MENU_ARRAY=('Reset' ': Reset CPU Scheduling Priority to system defaults')
					for ((i=$scale_value_highest; i>=$scale_value_lowest; i--))
					do

						if (( $i == $scale_value_lowest )); then

							desc='(Lowest priority)'

						elif (( $i == $scale_value_lower )); then

							desc='(Lower priority)'

						elif (( $i == $scale_value_low )); then

							desc='(Low priority)'

						elif (( $i == $scale_value_medium )); then

							desc='(Medium priority)'

						elif (( $i == $scale_value_high )); then

							desc='(High priority)'

						elif (( $i == $scale_value_higher )); then

							desc='(Higher priority)'

						elif (( $i == $scale_value_highest )); then

							desc='(Highest priority)'

						else

							desc=

						fi

						G_WHIP_MENU_ARRAY+=("$i" ": $desc")

					done

					G_WHIP_DEFAULT_ITEM=$cpu_priority
					if G_WHIP_MENU "Please select the desired CPU Scheduling Priority level for: ${aSERVICE_NAME[$MENU_SERVICE_INDEX]}\n
 - Lower values are low priority\n - Higher values are high priority"; then

						Apply_Process_Tool "$MENU_SERVICE_INDEX" 3 "$G_WHIP_RETURNED_VALUE"

					fi

				;;

				'I/O Scheduling Class')

					G_WHIP_MENU_ARRAY=('Reset' ': Reset I/O Scheduling Class to system defaults')
					for i in "${!aIO_CLASS_TYPE[@]}"
					do

						G_WHIP_MENU_ARRAY+=("${aIO_CLASS_TYPE[$i]}" ": ${aIO_CLASS_DESC[$i]}" )

					done

					G_WHIP_DEFAULT_ITEM=$io_class
					if G_WHIP_MENU "Please select the desired I/O Scheduling Class for: ${aSERVICE_NAME[$MENU_SERVICE_INDEX]}\n
NB: This only has an effect on drives handled by the CFQ scheduler.\nRead about I/O scheduling:
 - https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/performance_tuning_guide/ch06s04"; then

						Apply_Process_Tool "$MENU_SERVICE_INDEX" 4 "$G_WHIP_RETURNED_VALUE"

					fi

				;;

				'I/O Scheduling Priority')

					local desc

					G_WHIP_MENU_ARRAY=('Reset' ': Reset I/O Scheduling Priority to system defaults')
					for i in {0..7}
					do
						desc=
						if (( $i == $io_priority_default )); then

							desc=': (Default, based on I/O Scheduling Class and Nice level)'

						elif (( $i == 0 )); then

							desc=': (Highest priority)'

						elif (( $i == 7 )); then

							desc=': (Lowest priority)'

						fi

						G_WHIP_MENU_ARRAY+=("$i" ": $desc")

					done

					G_WHIP_DEFAULT_ITEM=$io_priority
					if G_WHIP_MENU "Please select the desired I/O Scheduling Priority level for: ${aSERVICE_NAME[$MENU_SERVICE_INDEX]}\n
NB: This only has an effect on drives handled by the CFQ scheduler."; then

						Apply_Process_Tool "$MENU_SERVICE_INDEX" 5 "$G_WHIP_RETURNED_VALUE"

					fi

				;;

				'Reset')

					Apply_Process_Tool "$MENU_SERVICE_INDEX" reset

				;;

				'Highest Priority')

					Apply_Process_Tool "$MENU_SERVICE_INDEX" 1 fifo
					Apply_Process_Tool "$MENU_SERVICE_INDEX" 3 50
					Apply_Process_Tool "$MENU_SERVICE_INDEX" 4 realtime

				;;

				'High Priority')

					Apply_Process_Tool "$MENU_SERVICE_INDEX" 1 reset
					Apply_Process_Tool "$MENU_SERVICE_INDEX" 2 -10
					Apply_Process_Tool "$MENU_SERVICE_INDEX" 4 reset
					Apply_Process_Tool "$MENU_SERVICE_INDEX" 5 0

				;;

				'Low Priority')

					Apply_Process_Tool "$MENU_SERVICE_INDEX" 1 reset
					Apply_Process_Tool "$MENU_SERVICE_INDEX" 2 10
					Apply_Process_Tool "$MENU_SERVICE_INDEX" 4 reset
					Apply_Process_Tool "$MENU_SERVICE_INDEX" 5 7

				;;

				'Lowest Priority')

					Apply_Process_Tool "$MENU_SERVICE_INDEX" 1 idle
					Apply_Process_Tool "$MENU_SERVICE_INDEX" 4 idle

				;;

			esac

		else

			MENU_TARGETID=0 # Return to main menu

		fi

	}

	#/////////////////////////////////////////////////////////////////////////////////////
	# Main
	#/////////////////////////////////////////////////////////////////////////////////////
	Load_Service_Array
	#-----------------------------------------------------------------------------------
	# Direct service control
	if [[ $INPUT_CMD ]]; then

		# Exit if input service could not be found
		[[ $INPUT_SERVICE && ! ${aSERVICE_NAME[0]} ]] && { G_DIETPI-NOTIFY 1 "Service ($INPUT_SERVICE) could not be found."; exit 1; }

		G_DIETPI-NOTIFY 3 "$G_PROGRAM_NAME" "$INPUT_CMD $INPUT_SERVICE"
		Apply_Service_State "$INPUT_CMD" ${INPUT_SERVICE:+0} # Load_Service_Array will add INPUT_SERVICE as index 0, if given

	#-----------------------------------------------------------------------------------
	# Men You!
	else

		Load_Process_Tool_Arrays

		until (( $MENU_TARGETID < 0 ))
		do

			if (( $MENU_TARGETID == 1 )); then

				Menu_Service

			else

				Menu_Main

			fi

		done

	fi
	#-----------------------------------------------------------------------------------
	exit 0
	#-----------------------------------------------------------------------------------
}
